#!/usr/bin/env python3
"""
S3 Object Put Tool - PutObject API 演示工具

这个工具演示如何使用 PutObject API 上传对象到 S3 存储桶。

使用示例:
    python s3_object_put.py --bucket my-bucket --key test.txt --file local.txt
    python s3_object_put.py --bucket my-bucket --key test.txt --content "Hello World"
    python s3_object_put.py --bucket my-bucket --key test.txt --file local.txt --content-type text/plain
    python s3_object_put.py --bucket my-bucket --key test.txt --file local.txt --verify-md5
    python s3_object_put.py --format json
    python s3_object_put.py --verbose
"""

import argparse
import boto3
import json
import sys
import hashlib
from datetime import datetime, timezone
from botocore.config import Config
from botocore.exceptions import ClientError, NoCredentialsError


class ObjectPutClient:
    """S3 对象上传客户端"""
    
    def __init__(self, endpoint_url='http://localhost:8000', 
                 access_key='AKIAIOSFODNN7EXAMPLE', 
                 secret_key='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
                 region='us-east-1'):
        """
        初始化对象上传客户端
        
        Args:
            endpoint_url: S3 服务端点
            access_key: AWS Access Key
            secret_key: AWS Secret Key
            region: AWS 区域
        """
        self.endpoint_url = endpoint_url
        self.access_key = access_key
        self.secret_key = secret_key
        self.region = region
        self._client = None
    
    def _get_client(self):
        """获取 S3 客户端"""
        if self._client is None:
            self._client = boto3.client(
                's3',
                endpoint_url=self.endpoint_url,
                aws_access_key_id=self.access_key,
                aws_secret_access_key=self.secret_key,
                region_name=self.region,
                config=Config(s3={'addressing_style': 'path'})
            )
        return self._client
    
    def put_object(self, bucket_name, key, body, content_type=None, metadata=None, verify_md5=False):
        """简单格式上传对象"""
        try:
            s3_client = self._get_client()
            
            # 准备参数
            put_args = {
                'Bucket': bucket_name,
                'Key': key,
                'Body': body
            }
            
            # Content-Type
            if content_type:
                put_args['ContentType'] = content_type
            
            # 自定义元数据
            if metadata:
                put_args['Metadata'] = metadata
            
            # Content-MD5 校验
            if verify_md5:
                md5_hash = hashlib.md5(body).hexdigest()
                put_args['ContentMD5'] = md5_hash
            
            print(f"📤 上传对象到 s3://{bucket_name}/{key}")
            print(f"   📊 数据大小: {len(body)} 字节")
            
            if content_type:
                print(f"   📄 Content-Type: {content_type}")
            
            if metadata:
                print(f"   🏷️  自定义元数据: {metadata}")
            
            if verify_md5:
                print(f"   🔐 Content-MD5: {md5_hash}")
            
            response = s3_client.put_object(**put_args)
            
            print(f"✅ 上传成功！")
            print(f"   🏷️  ETag: {response.get('ETag')}")
            if response.get('VersionId'):
                print(f"   📋 VersionId: {response.get('VersionId')}")
            
            return True
            
        except ClientError as e:
            error_code = e.response['Error']['Code']
            error_msg = e.response['Error']['Message']
            print(f"❌ 上传失败 [{error_code}]: {error_msg}")
            return False
        except NoCredentialsError:
            print("❌ 错误: 未找到 AWS 凭据")
            return False
        except Exception as e:
            print(f"❌ 未知错误: {e}")
            return False
    
    def put_object_verbose(self, bucket_name, key, body, content_type=None, metadata=None, verify_md5=False):
        """详细格式上传对象"""
        try:
            s3_client = self._get_client()
            
            print("📤 对象上传详细信息:")
            print("=" * 50)
            print(f"   🗂️  桶名称: {bucket_name}")
            print(f"   🔑 对象键名: {key}")
            print(f"   🌐 区域: {self.region}")
            print(f"   🔗 服务端点: {self.endpoint_url}")
            print(f"   📅 上传时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
            print(f"   📊 数据大小: {len(body)} 字节")
            
            if content_type:
                print(f"   📄 Content-Type: {content_type}")
            
            if metadata:
                print(f"   🏷️  自定义元数据: {metadata}")
            
            if verify_md5:
                md5_hash = hashlib.md5(body).hexdigest()
                print(f"   🔐 Content-MD5: {md5_hash}")
            
            print()
            
            # 准备参数
            put_args = {
                'Bucket': bucket_name,
                'Key': key,
                'Body': body
            }
            
            if content_type:
                put_args['ContentType'] = content_type
            
            if metadata:
                put_args['Metadata'] = metadata
            
            if verify_md5:
                put_args['ContentMD5'] = md5_hash
            
            print(f"   🔧 正在上传对象...")
            response = s3_client.put_object(**put_args)
            
            print(f"   ✅ 上传成功！")
            print(f"   🏷️  ETag: {response.get('ETag')}")
            if response.get('VersionId'):
                print(f"   📋 VersionId: {response.get('VersionId')}")
            
            print(f"   📊 上传完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
            
            return True
            
        except ClientError as e:
            error_code = e.response['Error']['Code']
            error_msg = e.response['Error']['Message']
            
            print(f"   ❌ 上传失败:")
            print(f"      错误代码: {error_code}")
            print(f"      错误信息: {error_msg}")
            
            return False
        except NoCredentialsError:
            print("   ❌ 错误: 未找到 AWS 凭据")
            return False
        except Exception as e:
            print(f"   ❌ 未知错误: {e}")
            return False
    
    def put_object_json(self, bucket_name, key, body, content_type=None, metadata=None, verify_md5=False):
        """JSON 格式上传对象"""
        try:
            s3_client = self._get_client()
            
            # 准备参数
            put_args = {
                'Bucket': bucket_name,
                'Key': key,
                'Body': body
            }
            
            if content_type:
                put_args['ContentType'] = content_type
            
            if metadata:
                put_args['Metadata'] = metadata
            
            if verify_md5:
                md5_hash = hashlib.md5(body).hexdigest()
                put_args['ContentMD5'] = md5_hash
            
            response = s3_client.put_object(**put_args)
            
            result = {
                "success": True,
                "bucket_name": bucket_name,
                "key": key,
                "size_bytes": len(body),
                "content_type": content_type,
                "metadata": metadata,
                "etag": response.get('ETag'),
                "version_id": response.get('VersionId'),
                "upload_time": datetime.now().isoformat()
            }
            
            if verify_md5:
                result["content_md5"] = md5_hash
            
            print(json.dumps(result, indent=2, ensure_ascii=False))
            return True
            
        except ClientError as e:
            error_code = e.response['Error']['Code']
            error_msg = e.response['Error']['Message']
            
            result = {
                "success": False,
                "bucket_name": bucket_name,
                "key": key,
                "error": error_code,
                "error_message": error_msg,
                "upload_time": datetime.now().isoformat()
            }
            
            print(json.dumps(result, indent=2, ensure_ascii=False))
            return False
        except NoCredentialsError:
            result = {
                "success": False,
                "error": "NoCredentials",
                "error_message": "未找到 AWS 凭据",
                "upload_time": datetime.now().isoformat()
            }
            print(json.dumps(result, indent=2, ensure_ascii=False))
            return False
        except Exception as e:
            result = {
                "success": False,
                "error": "Unknown",
                "error_message": str(e),
                "upload_time": datetime.now().isoformat()
            }
            print(json.dumps(result, indent=2, ensure_ascii=False))
            return False


def parse_args():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(
        description='S3 PutObject API 演示工具',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  %(prog)s --bucket my-bucket --key test.txt --file local.txt        # 从文件上传
  %(prog)s --bucket my-bucket --key test.txt --content "Hello"       # 直接上传内容
  %(prog)s --bucket my-bucket --key test.txt --file local.txt --content-type text/plain  # 指定类型
  %(prog)s --bucket my-bucket --key test.txt --file local.txt --verify-md5               # MD5校验
  %(prog)s --format json                                             # JSON 格式输出
  %(prog)s --verbose                                                 # 详细格式输出
  %(prog)s --access-key KEY --secret-key SECRET                      # 指定凭据
        """
    )
    
    parser.add_argument(
        '--host',
        default='http://localhost:8000',
        help='S3 服务端点 (默认: http://localhost:8000)'
    )
    
    parser.add_argument(
        '--access-key',
        default='AKIAIOSFODNN7EXAMPLE',
        help='AWS Access Key (默认: 测试密钥)'
    )
    
    parser.add_argument(
        '--secret-key',
        default='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
        help='AWS Secret Key (默认: 测试密钥)'
    )
    
    parser.add_argument(
        '--region',
        default='us-east-1',
        help='AWS 区域 (默认: us-east-1)'
    )
    
    parser.add_argument(
        '--format',
        choices=['simple', 'verbose', 'json'],
        default='simple',
        help='输出格式 (默认: simple)'
    )
    
    parser.add_argument(
        '--verbose',
        action='store_true',
        help='详细输出模式 (等同于 --format verbose)'
    )
    
    parser.add_argument(
        '--bucket',
        required=True,
        help='桶名称'
    )
    
    parser.add_argument(
        '--key',
        required=True,
        help='对象键名'
    )
    
    # 数据源选项
    data_group = parser.add_mutually_exclusive_group(required=True)
    data_group.add_argument(
        '--file',
        help='本地文件路径'
    )
    
    data_group.add_argument(
        '--content',
        help='直接指定内容'
    )
    
    parser.add_argument(
        '--content-type',
        help='Content-Type'
    )
    
    parser.add_argument(
        '--metadata',
        action='append',
        help='自定义元数据 key=value'
    )
    
    parser.add_argument(
        '--verify-md5',
        action='store_true',
        help='启用 Content-MD5 校验'
    )
    
    return parser.parse_args()


def main():
    """主函数"""
    args = parse_args()
    
    # 如果指定了 --verbose，覆盖 format 设置
    if args.verbose:
        args.format = 'verbose'
    
    # 准备数据
    if args.file:
        try:
            with open(args.file, 'rb') as f:
                body = f.read()
        except FileNotFoundError:
            print(f"❌ 错误: 文件不存在: {args.file}")
            sys.exit(1)
        except Exception as e:
            print(f"❌ 错误: 读取文件失败: {e}")
            sys.exit(1)
    else:
        body = args.content.encode('utf-8')
    
    # 准备元数据
    metadata = None
    if args.metadata:
        metadata = {}
        for meta in args.metadata:
            if '=' in meta:
                key, value = meta.split('=', 1)
                metadata[key] = value
            else:
                print(f"⚠️  警告: 忽略无效元数据格式: {meta}")
    
    # 创建客户端
    client = ObjectPutClient(
        endpoint_url=args.host,
        access_key=args.access_key,
        secret_key=args.secret_key,
        region=args.region
    )
    
    # 根据格式执行相应操作
    success = False
    if args.format == 'simple':
        success = client.put_object_simple(
            args.bucket, args.key, body, args.content_type, metadata, args.verify_md5
        )
    elif args.format == 'verbose':
        success = client.put_object_verbose(
            args.bucket, args.key, body, args.content_type, metadata, args.verify_md5
        )
    elif args.format == 'json':
        success = client.put_object_json(
            args.bucket, args.key, body, args.content_type, metadata, args.verify_md5
        )
    
    # 设置退出码
    sys.exit(0 if success else 1)


if __name__ == '__main__':
    main()
